package com.lg.meng;

import com.google.auto.service.AutoService;
import com.google.common.reflect.TypeToken;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;


@AutoService(Processor.class)
public class ReceiverInjectProcessor2 extends AbstractProcessor {
    private Messager messager;
    Map<String, List<VariableInfo>> classMap = new HashMap<>();
    Map<String, TypeElement> classTypeElementMap = new HashMap<>();
    private Filer filer;
    private Elements elementUtils;

    Map<String, ActionMethod> onReceiveClassMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnv.getMessager();
        filer = processingEnvironment.getFiler();
        elementUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(BindReceiver.class.getCanonicalName());
        supportTypes.add(OnReceive.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE, "ReceiverInjectProcessor process...");
        collectInfo(roundEnvironment);
        writeToFile();
        return true;
    }

    class ActionMethod{
        String methodName;
        List<String> actionList;

        public ActionMethod(String methodName, List<String> actionList) {
            this.methodName = methodName;
            this.actionList = actionList;
        }
    }
    private void collectInfo(RoundEnvironment roundEnvironment) {
        System.err.println("------------ReceiverInjectProcessor collectInfo");
        classMap.clear();
        classTypeElementMap.clear();
        onReceiveClassMap.clear();
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindReceiver.class);
        System.err.println("------------ReceiverInjectProcessor collectInfo elements.size()=" + elements.size());
        Set<? extends Element> onReceiveElements = roundEnvironment.getElementsAnnotatedWith(OnReceive.class);
        System.err.println("------------ReceiverInjectProcessor collectInfo onReceiveElements.size()=" + onReceiveElements.size());
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            String classFullName = typeElement.getQualifiedName().toString();
            System.err.println("------------classFullName:" + classFullName);
            List<VariableInfo> infoList = classMap.get(classFullName);
            if (infoList == null) {
                infoList = new ArrayList<>();
                classMap.put(classFullName, infoList);
                classTypeElementMap.put(classFullName, typeElement);
            }

            VariableInfo variableInfo = new VariableInfo();
            variableInfo.setVariableElement(variableElement);
            infoList.add(variableInfo);
        }

        try {
            for (Element element2 : onReceiveElements) {
                ExecutableElement executableElement = (ExecutableElement) element2;
                String name = executableElement.getSimpleName().toString();
                TypeElement enclosingElement = (TypeElement) element2.getEnclosingElement();
                String classFullName = enclosingElement.getQualifiedName().toString();
                System.err.println("------------classFullName:" +  enclosingElement.getSimpleName());
                Annotation annotation = element2.getAnnotation(OnReceive.class);
                System.err.println("------------annotation:" + annotation);
                Method annotationValue = OnReceive.class.getDeclaredMethod("actions");
                String[] actions = (String[]) annotationValue.invoke(annotation);


                ActionMethod actionMethod = new ActionMethod(name,Arrays.asList(actions));

                onReceiveClassMap.put(classFullName, actionMethod);
            }
        } catch (Exception e) {

        }
    }

    void writeToFile() {
        try {
            System.err.println("------------ReceiverInjectProcessor writeToFile");

            for (String classFullName : classMap.keySet()) {
                ActionMethod actionMethod = onReceiveClassMap.get(classFullName);
                System.err.println(classFullName + "------------actionMethod:" + actionMethod.methodName + "," + actionMethod.actionList.toString());
                TypeElement typeElement = classTypeElementMap.get(classFullName);
                // 使用构造函数绑定数据
                MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(ParameterSpec.builder(TypeName.get(typeElement.asType()), "host").build());

                MethodSpec.Builder unregistReceiverMethod = MethodSpec.methodBuilder("unregistReceiver")
                        .addModifiers(Modifier.PUBLIC);
                MethodSpec.Builder registReceiverMethod = MethodSpec.methodBuilder("registReceiver")
                        .addModifiers(Modifier.PUBLIC);
                List<VariableInfo> variableList = classMap.get(classFullName);

                List<FieldSpec> fieldSpecList = new ArrayList<>();


                for (VariableInfo variableInfo : variableList) {
                    VariableElement variableElement = variableInfo.getVariableElement();
                    // 变量名称(比如：TextView tv 的 tv)
                    String variableName = variableElement.getSimpleName().toString();
                    FieldSpec fieldSpec = FieldSpec.builder(Object.class, variableName, Modifier.PRIVATE).build();
                    fieldSpecList.add(fieldSpec);

                    constructor.addStatement("actionList = new java.util.ArrayList<>()");
                    for (String action : actionMethod.actionList) {
                        constructor.addStatement(" actionList.add(\"$L\")", action);
                    }

                    StringBuffer sb = new StringBuffer();
                    sb.append("host.$L = new android.content.BroadcastReceiver() {\n");
                    sb.append("@Override\n");
                    sb.append("public void onReceive(android.content.Context context, android.content.Intent intent) {\n");

                    sb.append("for(String action:actionList){\n");
                    sb.append("if(intent.getAction().equals(action)){\n");
                    sb.append("host.$L(context,intent);\n");
                    sb.append("return;\n");
                    sb.append("}\n");
                    sb.append("}\n");

                    sb.append(" }\n");
                    sb.append("}");

                    constructor.addStatement(sb.toString(), variableName,actionMethod.methodName);

                    constructor.addStatement("this.$L = host.$L", variableName, variableName);
                    constructor.addStatement("this.host = host", variableName);
                    constructor.addStatement("android.content.IntentFilter intentFilter = new android.content.IntentFilter()");
                    for (String action : actionMethod.actionList) {
                        constructor.addStatement("intentFilter.addAction(\"$L\")", action);
                    }
                    constructor.addStatement("this.intentFilter = intentFilter");

                    unregistReceiverMethod.addStatement("if($L != null)", variableName);
                    unregistReceiverMethod.addStatement("((android.content.Context)host).unregisterReceiver((android.content.BroadcastReceiver)$L)", variableName);

                    registReceiverMethod.addStatement("((android.content.Context)host).registerReceiver((android.content.BroadcastReceiver)$L,(android.content.IntentFilter)intentFilter)",variableName);
                }
                fieldSpecList.add(FieldSpec.builder(new TypeToken<List<String>>() {
                }.getType(), "actionList", Modifier.PRIVATE).build());
                fieldSpecList.add(FieldSpec.builder(Object.class, "host", Modifier.PRIVATE).build());
                fieldSpecList.add(FieldSpec.builder(Object.class, "intentFilter", Modifier.PRIVATE).build());
                // 构建Class
                TypeSpec typeSpec = TypeSpec.classBuilder(typeElement.getSimpleName() + "_ReceiverInjector")
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(constructor.build())
                        .addMethod(unregistReceiverMethod.build())
                        .addMethod(registReceiverMethod.build())
                        .addFields(fieldSpecList)
                        .addSuperinterface(ReceiverRegistor.class)
                        .build();

                // 与目标Class放在同一个包下，解决Class属性的可访问性
                String packageFullName = classFullName.substring(0,classFullName.lastIndexOf("."));
                System.err.println("------------packageFullName:" + packageFullName);
                JavaFile javaFile = JavaFile.builder(packageFullName, typeSpec)
                        .build();
                // 生成class文件
                javaFile.writeTo(filer);
            }
        } catch (Exception e) {

        }


    }
}
